/*
 * msguhc.c
 * 
 * Copyright 2014 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <syslog.h>
#include <stdarg.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

#include <msgserv.h> /* message_struct i deklaracje MSGLCD_* */
#include "lcd.h"

#define REFRESH 1
#define BUFF_LEN 256

#define MSGUHC_VERSION "0.2.1"
#define MY_NAME "msguhc"
#define PINFO(str, ...) PDEBUGL(0, str, ##__VA_ARGS__);
#define PDEBUG(str, ...) PDEBUGL(1, str, ##__VA_ARGS__);
#define PDEBUGL(x, str, ...) do{ if ( debug >= x ) _INTERNAL_LOGIT(str, ##__VA_ARGS__); }while(0)
#define PERROR(str, ...) do { printf("msguhc:%s[%s]%d: ", __FILE__, __FUNCTION__, __LINE__); printf(str, ##__VA_ARGS__); printf("error %d:%s \n", errno, strerror(errno)); }while(0)
#define _INTERNAL_LOGIT(str, ...) do { printf("msguhc:%s[%s]%d: ", __FILE__, __FUNCTION__, __LINE__); printf(str, ##__VA_ARGS__); printf("\n"); }while(0)

char *filename = NULL;
unsigned int messages=0; //number of messages stored at the server
struct message_struct *message = NULL;
int on_display[2][MAX_DISP_ROWS];
unsigned char lcd_rows; /* puuuuffff , ilosc rowsow lcd-eków, dynamicznie dostawona przez usb */
int sockfd;

in_addr_t server_ip = { 16777343 }; // 127.0.0.1
int debug = 0;
int timer_sec = 1;
int fitting = 1;
int active_msg, shift;
time_t refresh_time = 0;
unsigned char never_quit = 1;

static void safe_exit()
{
	msglcd_send(sockfd,MSGLCD_MESG_EXIT,3);
	LCD_close();
	close(sockfd);
	if (message) 
		free(message);
	exit(0);
}

#define fatal(format, ...) \
do {\
	PERROR(format, ##__VA_ARGS__);\
	safe_exit();\
} while (0)


static void sig_hup ()
{
	PINFO("Cought signal. Shutting down");
	safe_exit();
}

static void parse_cmd(int argc, char *argv[])
{
	int c=0;
	extern char *optarg;
	extern int optind;
	while (c != -1) {
		c=getopt(argc, argv, "hdbvVni:t:");
		switch (c) {
		case 'h':
			printf(MY_NAME
				": messaging system from msgsrv to usb pic module uhcpic.\n"
				"-h - help\n"
				"-b - fork to background\n"
				"-d - increase debug level\n"
				"-i xxx.xxx.xxx.xxx - server ip\n"
				"-V - print version\n"
				"-n - quit when somethign goes bad\n"
				"-t - set timer_sec \n"
				);
			exit(0);
		case 'V':
			printf("Version: %s\n", MSGUHC_VERSION);
			exit(0);
		case 'v':
		case 'd':
			debug++;
			break;
		case 'b':
			debug--;
			break;
		case 'i':
			server_ip=inet_addr(optarg);
			break;
		case 'n':
			never_quit = 0;
			break;
		case 't':
			timer_sec = atoi(optarg);
			break;
		}
	}
}

//----------------dsp-----------------

#define dsp_message(msg,row) LCD_print_line(row,(char *) message[msg].mesg)
     
static void dsp_rst()
{
	int i;
	active_msg=0;
	for (i=0;i<lcd_rows;i++)
		on_display[0][i]=-1;
	/* i dont need to free(message) cause i call realloc(), and the table
	 * will be free(), if needed to, anyway {if (message) free(message);} */
	messages = 0;
	fitting = 1;
	shift = 0;
}

static int dsp_refresh()
{
	int i;
	for (i=0;i<lcd_rows;i++) {
		if ( on_display[0][i]!=on_display[1][i] ) {
			if ( (on_display[0][i]=on_display[1][i]) == -1 ) {
				if (LCD_clr_line(i)<0)
					return(-1);
			} else {
				if ( dsp_message(on_display[0][i],i) < 0 )
					return(-2);
			}
		}
	}
	return(0);
}

static void dsp_shift_fitting() 
{
/* when the number of mesg
 * is lower then lcd_rows or 
 * the priorities change meet at lcd_rows
 * put all the mesg on the display */
	int i;
	for (i=0;i < lcd_rows;i++) {
		if ( i < messages )
			on_display[1][i] = i;
		else
			on_display[1][i] = -1;
	}
	active_msg=0;
}

static void dsp_shift_move() 
{
/* (look dsp_shift_nice) but when, these conditions
 * are false, we are "shifting" the messeges with
 * same priority through lines they take. */
	int i,j,l,h;
	if ( messages < lcd_rows )
		l = messages;
	else
		l = lcd_rows;
	h=l;
	while ( ( l>0 ) &&
		( message[l-1].pri == message[l].pri) )
		l--;
	while ( ( h<messages-1 ) &&
		( message[h-1].pri == message[h].pri) )
		h++;
	for (i=0;i<l;i++)
		on_display[1][i] = i;
	if ( ( active_msg < l ) || ( active_msg > h ) ) {
		active_msg=l;
	} else {
		if ( ++shift>=lcd_rows-l )
			shift=0;
		if ( ++active_msg>h )
			active_msg=l;
	}
	j=active_msg;
	for (i=0;i<lcd_rows-l;i++) {
		on_display[1][l+((i+shift)%(lcd_rows-l))] = j;
		if ( ++j>h ) j=l;
	}
}

static void dsp_shift()
{
	if ( fitting ) {
		dsp_shift_fitting();
	} else {
		dsp_shift_move();
	}
}

static int dsp_fitting() {
	/* checks, wheather messages fit to the screen 
	 * on the display (see dsp_shift_* ) */
	return ( ( messages<=lcd_rows ) || ( message[lcd_rows-1].pri < message[lcd_rows].pri ) ) ? 1 : 0;
}

//----------------dsp end------------------
//-----------------msg-------------------

static int msg_find(int client, char nr)
{
	int i;
	for ( i = 0 ; i < messages ; ++i )
		if ( ( message[i].client==client ) && ( message[i].nr==nr ) ) {
			return (i);
	}
	return (i);
}

static void msg_dump()
{
   int i,j;
   char *c;
   struct tm *time_rec;
   time_t tmp_time;
   if (messages==0)
     puts("Nothing to dump.");
   else {
      puts("Dumping messages");
      puts("+---+---+---------------|---+---+-----+----------------------------------------+\n"
           "|msg|cli|   IP address  |nr |pri|time |              text                      |\n"
           "+---+---+---------------|---+---+-----+----------------------------------------+");
      for (i=0;i<messages;i++) {
         tmp_time=(time_t) message[i].time;
         time_rec=localtime(&tmp_time);
         printf("|%3d|%3d|",
                i,message[i].client);
         for (j=15-strlen((c=inet_ntoa(message[i].addr)));j>0;j--)
           putc(' ',stdout);
         printf("%s",c);
         printf("|%3d|%3d|%.2d:%.2d|", message[i].nr, message[i].pri, (*time_rec).tm_hour,(*time_rec).tm_min);
         for (j=0;j<MSG_LEN;j++) {
            if ( message[i].mesg[j]<8 )
              putchar(message[i].mesg[j]+'0');
            else
              putchar(message[i].mesg[j]);
         }
         puts("|");
      }
      puts("+---+---+---------------|---+---+-----+----------------------------------------+");
      
      puts("Dumping display\n"
	   "+---+---+----------------------------------------+\n"
	   "|row|msg|                 text                   |\n"
	   "+---+---+----------------------------------------+");
      for (i=0;i<lcd_rows;i++) {
	 if (i<messages) {
	    printf("|%3d|%3d|",i,on_display[0][i]);
	    for (j=0;j<MSG_LEN;j++) {
	       if ( message[on_display[0][i]].mesg[j]<8 )
		 putchar(message[on_display[0][i]].mesg[j]+'0');
	       else
		 putchar(message[on_display[0][i]].mesg[j]);
	    }
	    puts("|");
	 }
	 else
	   puts("|   |   |                                        |");
      }
      puts("+---+---+----------------------------------------+");
   }
}

static void msg_shift(int mesg, int direct)
{
	int i;
	if ( (mesg<0) || (mesg>=messages) )
		return;
	
	
	message[mesg+direct] = message[mesg];
	for (i=lcd_rows-1; i>=0; --i) {
		if ( on_display[0][i] == mesg ) {
			on_display[0][i]=on_display[0][i]+direct;
			break;
		}
	}
	
}

static void msg_delete(int mesg)
{
	int i;
	/*if ( (mesg < 0) || (mesg > messages) )  {
		PERR("error in msgdelete");
		return;
	}*/
	--messages;
	/* copy messages to remove the last messege[i] */
	for(i = mesg; i < messages; ++i) {
		message[i] = message[i+1];
	}
	/* deallocate memory */
	message = realloc(message, sizeof(*message)*messages);
	if ( messages && NULL == message ) fatal("error allocating memory");
	/* clear line of lcds */
	for (i=0;i<lcd_rows;i++) {
		if ( on_display[0][i] > mesg ) {
			on_display[0][i]--;
		} else if ( on_display[0][i] == mesg ) {
			PDEBUG("cleaning line %d.",i);
			LCD_clr_line(i);
			on_display[0][i] = -1;
		}
	}
	
	/* check if message nice fit in display*/
	fitting = dsp_fitting();
	if (fitting) {
		dsp_shift_fitting();
		dsp_refresh();
	} else {
		refresh_time = 0;
	}
}

#define reorder_test(mesg_a,mesg_b) ( ( mesg_a.pri>mesg_b.pri ) || \
	 ( ( mesg_a.pri==mesg_b.pri ) && ( mesg_a.client>mesg_b.client ) ) || \
	 ( ( mesg_a.pri==mesg_b.pri ) && ( mesg_a.client==mesg_b.client ) && ( mesg_a.nr>mesg_b.nr ) ) )

static int msg_order_new_message(struct message_struct new_mesg)
{
	int i;
	
	if ( messages < 0 ) return -100;
	
	i = messages;
	
	/* alloc new space for messages */
	++messages;
	message = realloc(message, sizeof(*message)*messages);
	if (NULL == message ) fatal("error allocating memory");
	
	PDEBUGL(2, "messages:%d i:%d", messages, i);
	if ( messages > 1 ) {
		/* if we have more then 1 messages, we should 
		 * put the new message in the proper place */
		if ( ( reorder_test( message[i-1], new_mesg)) ) {
			do {
				msg_shift(--i, +1);
			} while ( ( i>0 ) && ( reorder_test ( message[i-1], new_mesg ) ) );
		}
	} else {
		on_display[0][0] = 1;
	}
	
	return i;
}

static int msg_update_client_messages(struct message_struct *mesg, int len)
{
	int i, j;
	switch ( len ) {
	case 3 + sizeof (struct message_struct):
		
		i = msg_find( mesg->client, mesg->nr);
		if ( debug ) {
			if ( i == messages ) 
				PDEBUGL(3, "Adding: %d ", i);
			else 
				PDEBUGL(3, "Updating: %d", i);
		}
		if (i == messages) {
			i = msg_order_new_message(*mesg);
			if ( i < 0 ) {
				PDEBUGL(3, "msg_orger_new_massgeagagnjn failed");
				return(-3);
			}
			/* check if message nice fit in display*/
			fitting = dsp_fitting();
			if (fitting) {
				dsp_shift_fitting();
			} else {
				refresh_time = 0;
			}
		}
		message[i] = *mesg;
		for(j = lcd_rows-1; j >= 0 ; j-- ) {
			if (on_display[0][j] == i)
				break;
		}
		if (j > -1) {
			on_display[0][j] = i;
			if ( dsp_message(i, j) < 0 ) 
				return -4;
			break;
		}
		if ( fitting ) {
			if ( dsp_refresh() < 0 ) 
				return -5;
		}
		break;
	case 3 + sizeof (struct message_struct) - MSG_LEN:
		i = msg_find( mesg->client , mesg->nr );
		if ( i == messages ) {
			PDEBUGL(1, "Message to delete not found: %d %d %d", mesg->client, mesg->nr, len);
			return (-1);
		}
		PDEBUGL(1, "Deleting mesg # %d",i);
		msg_delete(i);
		break;
	default:
		PDEBUG("invalid lenght.");
		return (-1);
	}
	return 0;
}

static int msg_delete_client_messages(struct message_struct *mesg, int len)
{
	int i;
	
	/* check length, needs to be 7 */
	if ( len != 7 ) {
		PINFO("invalid lenght of packet.");
		return(-1);
	}
	
	PDEBUG("Deletting messages from client %d ", mesg->client);
	i = 0;
	while (i < messages) {
		if ( message[i].client == mesg->client ) {
			msg_delete(i);
		} else {
			i++;
		}
	}
	
	return 0;
}

static int msg_meng(char *buff, int len)
{
	switch (buff[1]) {
	case MSG_UP: 
		return msg_update_client_messages( (struct message_struct *) (buff+3), len );
	case MSG_DEL:
		return msg_delete_client_messages( (struct message_struct *) (buff+3), len);
	case MSG_LCD:
		if ( buff[2] != '#' ) return(-10);
		if ( buff[3] != '#' ) return(-10);
		if ( buff[4] != '#' ) return(-10);
		PDEBUGL(5, "Sending a message straight to lcds.");
		if ( LCD_print(buff+5, len-5) < 0) 
			return -6;
		break;
	default:
		PINFO("Unknown packet received.");
		return(-1);
	}
	return(0);
}

// ----------------------msg end---------------------

static int wait_for_any_message()
{
	fd_set set;
	struct timeval tv;
	int ret;
	
	for(;;) {
		FD_ZERO(&set);
		FD_SET(sockfd,&set);
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		ret = select ( sockfd+1,&set,NULL,NULL,&tv );
		if ( !LCD_ok() )
			return -1;
		if (ret > 0) {
			return 1;
		} else if (ret == -1 && errno != EINTR) {
			return -1;
		}
		PDEBUG("waited for some seconds. nothing");
	}
	return 1; /* silent gcc error */
}

static void reconnect_on_error(const char *format, ...)
{
	int i;
	va_list va;
	char msg[256];
	va_start (va, format);
	vsnprintf (msg, sizeof(msg), format, va);
	msg[sizeof(msg) - 1] = 0;
	PDEBUG("%s", msg);
	
	if ( !never_quit )
		safe_exit();
	
	msglcd_send(sockfd,MSGLCD_MESG_EXIT,3);
	close(sockfd);
	
	while (messages) {
		msg_delete_client_messages(&message[0], 7);
	}
	
	sleep(1);
	
	if ( !LCD_ok() ) {
		do {
			sleep (5);
			i = LCD_init();
			PINFO("reconnecting to lcds erorr: %d LCD_ok()=%d", i, LCD_ok());
		} while ( !LCD_ok() );
		PINFO(" Reconnected to LCDs");
		dsp_rst();
	}
	
	do {
		i = LCD_ok();
		PINFO("reconnecting to msglcd, while LCD_ok()=%d", i);
		sockfd = msglcd_socket_connect(server_ip, MSG_CONNECT_LCD);
		if ( i ) {
			LCD_rst();
			for(i=0;i<lcd_rows/2;i++) {
				LCD_print_line(i*2,   "            Siemanko! :D :D :* :) :p       ");
				LCD_print_line(i*2+1, "     Nie moge sie polaczyc z msgsrv :(     ");
			}
		}
		sleep(5);
	} while( sockfd < 0 );
	for(i=0;i<lcd_rows;i++) {
		LCD_print_line(i, "                                         ");
	}
	return;
	
	PERROR("Could not reconnect. Exiting");
	dsp_shift();
	if (dsp_refresh() < 0) fatal("reconnect on error: dsp_refresh");
	safe_exit();
}

int daemonize(void) 
{
	int PID;
	PID = fork();
	switch ( PID ) {
	case 0:
		fclose(stderr);
		fclose(stdin);
		fclose(stdout);
		break;
	case -1:
		break;
	default:
		exit(0);
	}
	return(PID);
}

int main(int argc, char *argv[])
{
	char buff[BUFF_LEN];
	int i;
	PDEBUGL(2, "Straging up");
	server_ip=inet_addr("127.0.0.1");
	parse_cmd(argc,argv);

	if ( !server_ip ) {
		fprintf(stderr, "No server ip given.");
		safe_exit();
	}
	
	signal(SIGHUP, sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	sockfd = msglcd_socket_connect(server_ip, MSG_CONNECT_LCD);
	i = LCD_init();
	
	if ( sockfd < -1 ) {
		fprintf(stderr, "lcd_socket_connect sockfd=%d ", sockfd);
		safe_exit();
	}
	
	if ( sockfd < 0 || i < 0 ) {
		reconnect_on_error("Could not connect sockfd=%d LCD_init()=%d ", sockfd, i);
	}
	
	if ( debug < 0 )
		daemonize();
	
	PDEBUGL(2, "im in the loop ");
	for (;;) {
		PDEBUGL(3, "waiting for messages from server");
		if ( wait_for_any_message() < 0 ) {
			reconnect_on_error("test_fd error.");
			continue;
		}
		
		i = msglcd_recv(sockfd, buff, BUFF_LEN);
		
		PDEBUGL(3, "Got message with length %d.", i);
		if (debug >= 5) {
			int k;
			printf("CHR:");
			if ( i==79 ) {
				int j;
				struct message_struct message;
				memcpy(&message,  buff+3, sizeof(struct message_struct));
				
				struct tm *time_rec;
				time_t tmp_time = (time_t) message.time;
				time_rec = localtime(&tmp_time);
				printf("|%3d|%3d|",i,message.client);
				printf("|%3d|%3d|%.2d:%.2d|", message.nr, message.pri, (*time_rec).tm_hour,(*time_rec).tm_min);
				for (j=0;j<MSG_LEN;j++) {
					if ( message.mesg[j]<8 )
						putchar(message.mesg[j]+'0');
					else
						putchar(message.mesg[j]);
				}
				puts("|");
			} else {
				for( k=0; k<i ; k++) 
					if ( buff[k] > 31 && buff[k] < 128) printf("%c", buff[k]);
					else printf("%c", buff[k]);
			}
			printf("\nHEX");
			for( k=0; k<i/2 ; k++) printf("|%x", buff[k]);
			printf("\nHE2");
			for ( k=i/2; k<i;k++) printf("|%x", buff[k]);
			printf("\n");
		}
		
		switch ( i ) {
		case -1:
			reconnect_on_error("internal error");
			continue;
		case -2:
			reconnect_on_error("buff size too small. Probably a bigger error");
			continue;
		case -3:
			reconnect_on_error("invalid messege length");
			continue;
		case -4:
			reconnect_on_error("Parity error while receiving from the client %d.");
			continue;
		case -5:
			reconnect_on_error("serverclosed connection to me.");
			continue;
		case MSGLCD_MESG_LENGTH:
			if ( !strncmp(buff+2, MSGLCD_MESG_OK, 3) ) {
				PDEBUGL(2, "server sended ok message");
			} else if ( !strncmp(buff+2, MSGLCD_MESG_EXIT, 3) ) {
				reconnect_on_error("Server closed the connection.");
			} else if ( !strncmp(buff+2, MSGLCD_MESG_PING, 3) ) {
				PDEBUGL(2, "pinged. sending ok");
				if ( msglcd_send(sockfd,MSGLCD_MESG_OK,3) < 0 ) {
					reconnect_on_error("error sending the replay");
				}
			} else {
				reconnect_on_error("Strange mesg received from the server");
			}
			continue;
		}
		
		i = msg_meng(buff+2,i-4);
		switch ( i ) {
			case -1:
			case -2:
				reconnect_on_error("Error processing of the request. ret=%d", i);
				continue;
			case -3:
			case -4:
			case -5:
				reconnect_on_error(" display comunication error \n");
				break;
		}
		
		i = msglcd_send(sockfd,MSGLCD_MESG_OK,3);
		if ( i < 0 ) {
			reconnect_on_error("error sending the replay: ret=%d ", i);
			continue;
		}
		
		if (!fitting || debug) {
			if ( refresh_time < time(NULL) ) {
				refresh_time = time(NULL) + REFRESH;
				dsp_shift();
				if (dsp_refresh() < 0)  {
					 reconnect_on_error(" display comunication error dsp_refresh \n");
				}
			}
		}
		
	}
	
	return (0);
}

